from Bases import setID
from Bases import OSCBaseObject
from Parameter import *

class AttributeBase(OSCBaseObject):
    '''Base for all attributes.
    If initialized directly, it functions as as global attribute.
    Events and xml creation/parsing is done through this class.
    
    :Parameters:
        'name' : default is empty
        'id' : Universal ID.  Default is None (which assigns a new uuid)
        'class_name' : class specification (in progress)
        'categories' : set of categories the attribute belongs to 
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.'
    '''
    _saved_class_name = 'AttributeBase'
    _saved_attributes = ['base_class', 'id']
    _saved_child_objects = ['indexed_parameters']
    base_class = 'Attribute'
    signals_to_register = ['id_changed', 'new_indexed_parameter', 'del_indexed_parameter']
    def __init__(self, **kwargs):
        self._id = None
        super(AttributeBase, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            self.id = setID(kwargs.get('id'))
            self.indexed_parameters = {}
            
    @property
    def id(self):
        return self._id
    @id.setter
    def id(self, value):
        old = self.id
        self._id = value
        if old != value:
            self.emit('id_changed', obj=self, old=old, new=value)
        
    def _deserialize_child(self, d):
        for cls in [GlobalParameter, DefinitionParameter, PatchedParameter]:
            if d['saved_class_name'] == cls._saved_class_name:
                return cls(parent_attribute=self, deserialize=d)
        return super(AttributeBase, self)._deserialize_child(d)

class GlobalAttribute(AttributeBase):
    _saved_class_name = 'GlobalAttribute'
    #_saved_attributes = ['name', 'initial_value']
    _Properties = {'name':dict(default=''), 'initial_value':dict(default=0)}
    _SettingsProperties = ['name', 'initial_value']
    def __init__(self, **kwargs):
        self._name = None
        super(GlobalAttribute, self).__init__(**kwargs)
        self.bind(name=self.on_name_set)
        if 'deserialize' not in kwargs:
            self.name = kwargs.get('name', '')
        #if not hasattr(self, 'initial_value'):
        #    self.initial_value = None
        if self.base_class == 'GlobalAttribute':
            self.id = self.name
#        self.register_attr_watch('name')
#    @property
#    def name(self):
#        return self._name
#    @name.setter
#    def name(self, value):
#        self._name = value
#        self.set_osc_address(value)
        
    def on_name_set(self, **kwargs):
        self.set_osc_address(kwargs.get('value'))
            
    def add_indexed_parameter(self, **kwargs):
        '''
        :Parameters;
            'name' :
            'val_min' : 
            'val_max' :
        '''
        kwargs.update({'parent_attribute':self})
        self.add_child_object(kwargs)
        param = GlobalParameter(**kwargs)
        self.indexed_parameters.update({param.id:param})
        self.emit('new_indexed_parameter', attribute=self, parameter=param)
        return param

class DefinitionAttribute(AttributeBase):
    '''Attribute that is attached to a device definition.
    
    :Parameters:
        'parent_device_id' : id of the patched device this is attached to.
            default is None
        'chan_index' : int, default is 1
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    '''
    _saved_class_name = 'DefinitionAttribute'
    _saved_attributes = ['parent_device_id', 'chan_index', '_initial_value', 
                         'GlobalAttribute_id', '_name']
    _Properties = {'name':dict(type=str, fget='_name_getter'), 
                   'initial_value':dict(type=int, fget='_initial_value_getter'), 
                   'sixteen_bit':dict(default=False, fvalidate='_sixteen_bit_validate')}
                   #'chan_index':dict(type=int)}
    _SettingsProperties = ['name', 'initial_value', 'Index', 'sixteen_bit']
    def __init__(self, **kwargs):    
        self.parent_device = kwargs.get('parent_device')
        #self._chan_index = None
        self._name = None
        self._initial_value = None
        self.GlobalAttribute = kwargs.get('GlobalAttribute')
        if 'chan_index' in kwargs:
            kwargs['Index'] = kwargs['chan_index']
        super(DefinitionAttribute, self).__init__(**kwargs)
        #self.bind(Index=self.on_Index_set, chan_index=self.on_chan_index_set)
        #self.register_signal('chan_index_changed', 'attr_watch_name')
        #self.GlobalAttribute.connect('attr_watch_name', self.on_gl_attrib_attr_watch_signal)
        if 'deserialize' not in kwargs:
            self.parent_device_id = kwargs.get('parent_device_id')
            self.chan_index = kwargs.get('chan_index', 1)
        for c in self.GlobalAttribute.categories.itervalues():
            if c.id not in self.categories:
                self.add_category(c)
#        p_kwargs = {'parent_attribute':self}
#        self.add_child_object(p_kwargs)
#        for key, val in self.GlobalAttribute.indexed_parameters.iteritems():
#            if key not in self.indexed_parameters:
#                p_kwargs.update({'GlobalParameter':val})
#                param = DefinitionParameter(**p_kwargs)
#                self.indexed_parameters.update({param.id:param})
        self.GlobalAttribute.bind(property_changed=self.on_gl_attrib_property_changed)
        self.bind(name=self._on_name_set, initial_value=self._on_initial_value_set)
    
#    def on_gl_attrib_attr_watch_signal(self, **kwargs):
#        signal = kwargs.get('signal_name')
#        if signal in self._emitters:
#            self.emit(signal, **kwargs)
            
    def on_gl_attrib_property_changed(self, **kwargs):
        new_kwargs = kwargs.copy()
        new_kwargs['obj'] = self
        self.emit('property_changed', **new_kwargs)
    
    @property
    def GlobalAttribute_id(self):
        return self.GlobalAttribute.id
    @GlobalAttribute_id.setter
    def GlobalAttribute_id(self, value):
        pass
        
#    @property
#    def name(self):
#        if self._name is not None:
#            return self._name
#        return self.GlobalAttribute.name
#    @name.setter
#    def name(self, value):
#        self._name = value
#        self.set_osc_address(value)
        
    def _name_getter(self):
        if self._name is not None:
            return self._name
        return self.GlobalAttribute.name
        
    def _on_name_set(self, **kwargs):
        value = kwargs.get('value')
        self._name = value
        self.set_osc_address(value)
        
    def _initial_value_getter(self):
        if self._initial_value is not None:
            return self._initial_value
        return self.GlobalAttribute.initial_value
        
    def _on_initial_value_set(self, value):
        self._initial_value = value
        
    def _sixteen_bit_validate(self, value):
        if not value:
            return False
        return self.chan_index + 1 not in self.parent_device.Attributes.indexed_items
        
    def _on_sixteen_bit_set(self, **kwargs):
        if kwargs.get('value'):
            if self.parent_device.channel_count < self.chan_index + 1:
                self.parent_device.channel_count += 1
        else:
            if self.parent_device.channel_count == self.chan_index + 1:
                self.parent_device.channel_count -= 1
        
    @property
    def chan_index(self):
        return self.Index
    @chan_index.setter
    def chan_index(self, value):
        self.Index = int(value)
        
#    def on_Index_set(self, **kwargs):
#        value = kwargs.get('value')
#        if value != self.chan_index:
#            self.chan_index = value
#            
#    def on_chan_index_set(self, **kwargs):
#        value = kwargs.get('value')
#        if value != self.Index:
#            self.Index = value
            
#    @property
#    def chan_index(self):
#        return self._chan_index
#        
#    @chan_index.setter
#    def chan_index(self, value):
#        value = int(value)
#        self._chan_index = value
#        if 'chan_index_changed' in self._emitters:
#            self.emit('chan_index_changed', attribute=self, chan=value)
#        
#    @property
#    def initial_value(self):
#        if getattr(self, '_initial_value', None) is not None:
#            return self._initial_value
#        return self.GlobalAttribute.initial_value
#    @initial_value.setter
#    def initial_value(self, value):
#        self._initial_value = value
            
    def add_indexed_parameter(self, **kwargs):
        '''
        :Parameters:
            'global_parameter' : 
            'global_parameter_id' :
        '''
        gparam = kwargs.get('global_parameter')
        gparam_id = kwargs.get('global_parameter_id')
        if not gparam:
            gparam = self.GlobalAttribute.indexed_parameters[gparam_id]
        p_kwargs = dict(parent_device=self, GlobalParameter=gparam)
        self.add_child_object(p_kwargs)
        param = DefinitionParameter(**p_kwargs)
        self.indexed_parameters.update({param.id:param})
        self.emit('new_indexed_parameter', attribute=self, parameter=param)
        return param
        
    def del_indexed_parameter(self, **kwargs):
        pid = kwargs.get('id')
        param = self.indexed_parameters.get(pid)
        if param:
            del self.indexed_parameters[pid]
            self.emit('del_indexed_parameter', attribute=self, parameter=param, id=pid)

class PatchedAttribute(AttributeBase):
    '''Attribute that is attached to a patched device.
    
    :Parameters:
        'universe_id' : DMX universe this attribute is patched to.
        'chan' : DMX channel the attribute is patched to.
        'value' : int, raw DMX value.  default is 0
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    
    :Events:
        value_changed(attribute, value)
    '''
    
    _saved_class_name = 'PatchedAttribute'
    _saved_attributes = ['universe_id', 'chan', 'DefinitionAttribute_id', '_value']
    _Properties = {'active_parameter':dict(fset='_set_active_parameter'), 
                   'value':dict(default=0, ignore_type=True, quiet=True, 
                                value_min=0, value_max=255)}
    def __init__(self, **kwargs):
        self._value = 0
        #self._active_parameter = None
        self.DefinitionAttribute = kwargs.get('DefinitionAttribute')
        self.lsb_attribute = None
        self.value_set_by_osc = False
        self.value_set_by_group = False
        self.users = set()
        self.captured_by = None
        #self.parameters_by_name = {}
        super(PatchedAttribute, self).__init__(**kwargs)
        self.register_signal('value_changed', 'parameter_changed')
        if 'deserialize' not in kwargs:
            self.DefinitionAttribute_id = self.DefinitionAttribute.id
            #self.patch_data = kwargs.get('patch_data')
            self.universe_id = kwargs.get('universe_id')
            self.chan = kwargs.get('chan')
        for c in self.DefinitionAttribute.categories.itervalues():
            if c.id not in self.categories:
                self.add_category(c)
        p_kwargs = {'parent_attribute':self}
        self.add_child_object(p_kwargs)
        for dparam in self.DefinitionAttribute.indexed_parameters.itervalues():
            if dparam.name not in self.indexed_parameters:
                p_kwargs.update({'DefinitionParameter':dparam})
                param = PatchedParameter(**p_kwargs)
                self.indexed_parameters.update({param.name:param})
        #for param in self.indexed_parameters.itervalues():
        #    self.parameters_by_name.update({param.name:param})
        #self.value = kwargs.get('value', 0)
        if self.sixteen_bit:
            self.Properties['value'].type = float
            self._add_lsb_attribute()
        init_val = self.DefinitionAttribute.initial_value
        if init_val is not None:
            self.value = init_val
            self._find_active_parameter()
        self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value, 
                                        'current-value':self.on_osc_request_value})
        if not self.osc_node.oscMaster:
            self.osc_node.send_message(address='current-value', to_master=True)
        self.bind(value=self.on_value_set)
        
    @property
    def name(self):
        return self.DefinitionAttribute.name
    @property
    def chan_index(self):
        return self.Index
    @property
    def sixteen_bit(self):
        return self.DefinitionAttribute.sixteen_bit
        
    def _add_lsb_attribute(self):
        self.lsb_attribute = LSBAttribute(parent_attribute=self)
        
#    @property
#    def value(self):
#        return self._value
#    @value.setter
#    def value(self, value):
#        value = int(value)
#        if value > 255:
#            return
#        if value != self._value:
#            self._value = value
#            self.on_value_change()
    
    #def _value_validate(self, value):
    #    return value >= 0 and value <= 255
        
    def on_value_set(self, **kwargs):
        self.on_value_change()
        
#    @property
#    def active_parameter(self):
#        return self._active_parameter
#            
#    @active_parameter.setter
#    def active_parameter(self, value):
#        param = self.indexed_parameters.get(value)
#        if param:
#            param.active=True
#        #print 'attribute parameter: ', value

    def _set_active_parameter(self, value):
        param = self.indexed_parameters.get(value)
        if param:
            param.active=True
        
    def repatch(self, **kwargs):
        self.universe_id = kwargs.get('universe_id')
        start = kwargs.get('start_chan')
        self.chan = start + self.chan_index - 1
    
    def _find_active_parameter(self):
        result = None
        for param in self.indexed_parameters.itervalues():
            if param is not None and param.active:
                if param != self.active_parameter:
                    #self._active_parameter = param
                    self.Properties['active_parameter'].value = param
                    self.emit('parameter_changed', attribute=self, name=param.name, parameter=param, index=param.index)
                return param
                
    def send_value_to_osc(self):
        if self.osc_enabled:
            self.osc_node.send_message(address='set-value', value=self.value)
            
    def on_osc_set_value(self, **kwargs):
        values = kwargs.get('values')
        if len(values):
            value = values[0]
            self.value_set_by_osc = True
            #print 'attribute set by osc:', kwargs.get('address'), values
            self.value = value
            
    def on_osc_request_value(self, **kwargs):
        if self.osc_node.oscMaster:
            self.send_value_to_osc()
    
    def on_value_change(self):
        self._find_active_parameter()
        if not self.value_set_by_osc and not self.value_set_by_group:
            self.send_value_to_osc()
        if 'value_changed' in self._emitters:
            self.emit('value_changed', attribute=self, value=self.value)
        self.value_set_by_osc = False
        self.value_set_by_group = False
        
    def add_user(self, user):
        self.users.add(user)
    def del_user(self, user):
        self.users.discard(user)
    def update_user_value(self, user=None, id=None):
        if id is None:
            id = self.name.lower()
        if user:
            value = user.member_values[id]
            self.captured_by = user.id
            #print 'ltp=', value
        else:
            value = max([u.member_values[id] for u in self.users if id in u.member_values])
            self.captured_by = None
            #print 'htp=', value
        #print 'attribute:', self.value
        self.value_set_by_group = True
        self.value = value
        
class LSBAttribute(AttributeBase):
    _Properties = {'value':dict(default=0, quiet=True)}
    def __init__(self, **kwargs):
        self.parent_attribute = kwargs.get('parent_attribute')
        kwargs.setdefault('id', self.parent_attribute.id + 'fine')
        kwargs.setdefault('Index', self.parent_attribute.Index + 1)
        super(LSBAttribute, self).__init__(**kwargs)
        self.register_signal('value_changed')
        self.parent_attribute.bind(value=self.on_parent_attribute_value_set)
        self.bind(value=self._on_value_set)
        
    @property
    def name(self):
        return self.parent_attribute.name + 'fine'
    @property
    def universe_id(self):
        return self.parent_attribute.universe_id
    @property
    def chan(self):
        return self.parent_attribute.chan + 1
    
    def on_parent_attribute_value_set(self, **kwargs):
        value = kwargs.get('value')
        self.value = int((value % 1.) * 255.)
        
    def _on_value_set(self, **kwargs):
        self.emit('value_changed', attribute=self, value=kwargs.get('value'))
